/*
Copyright (C) 2013 Matthew Netsch

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "passgen.h"

#include <QTime>
#include <QIntValidator>

int
log2(unsigned int pow2)
{
    int result = -1;

    while (pow2)
    {
        pow2 >>= 1;
        result++;
    }

    return result;
}

PassGen::PassGen(QWidget *parent) :
    QDialog(parent),
    maxlength(10),
    enables(UPPER|LETTER|NUMBER|SPECIAL),
    num_enables(log2(LAST)),
    show_pass(false),
    top(NULL),
    buttons(NULL),
    options(NULL),
    OK(NULL),
    cancel(NULL),
    showPass(NULL),
    useLetters(NULL),
    useUpper(NULL),
    useNumbers(NULL),
    useSpecial(NULL),
    maxLabel(NULL),
    maxEdit(NULL)
{
    QTime time = QTime::currentTime();
    qsrand((uint)time.msec());

    initState();
}

PassGen::~PassGen()
{
    delete top;
    delete buttons;
    delete options;
    delete OK;
    delete cancel;
    delete showPass;
    delete useLetters;
    delete useUpper;
    delete useNumbers;
    delete useSpecial;
    delete maxLabel;
    delete maxEdit;
}

void
PassGen::initState()
{
    setModal(true);

    top = new QVBoxLayout();
    buttons = new QHBoxLayout();
    options = new QVBoxLayout();
    OK = new QPushButton();
    cancel = new QPushButton();
    showPass = new QCheckBox();
    useLetters = new QCheckBox();
    useUpper = new QCheckBox();
    useNumbers = new QCheckBox();
    useSpecial = new QCheckBox();
    maxLabel = new QLabel();
    maxEdit = new QLineEdit();

    showPass->setText("Show passwords");
    useLetters->setText("Use letters");
    useUpper->setText("Use uppercase");
    useNumbers->setText("Use numbers");
    useSpecial->setText("Use special characters");

    showPass->setToolTip("Option to hide passwords to foil screenshot spyware and screen-lookers a like");
    useLetters->setToolTip("Use letters to generate random passwords");
    useUpper->setToolTip("Use uppercase letters to generate random passwords");
    useNumbers->setToolTip("Use numbers to generate random passwords");
    useSpecial->setToolTip("Use special characters to generate random passwords");

    showPass->setChecked(false);
    useLetters->setChecked(true);
    useUpper->setChecked(true);
    useNumbers->setChecked(true);
    useSpecial->setChecked(true);

    maxLabel->setText("Generated password length:");
    maxEdit->setText(QString::number(maxlength));

    maxEdit->setValidator(new QIntValidator(0, 64));

    OK->setText("OK");
    cancel->setText("Cancel");

    connect(OK, SIGNAL(clicked()), this, SLOT(accept()));
    connect(cancel, SIGNAL(clicked()), this, SLOT(reject()));

    buttons->addWidget(OK);
    buttons->addWidget(cancel);

    options->addWidget(showPass);
    options->addWidget(useLetters);
    options->addWidget(useUpper);
    options->addWidget(useNumbers);
    options->addWidget(useSpecial);
    options->addSpacing(5);
    options->addWidget(maxLabel);
    options->addSpacing(2);
    options->addWidget(maxEdit);

    top->addItem(options);
    top->addItem(buttons);

    setLayout(top);
    setWindowTitle("Options");
    this->setWindowIcon(QIcon(":/icons/options.png"));

    connect(this, SIGNAL(accepted()), this, SLOT(commit()));
}

void
PassGen::commit()
{
    if (useLetters->isChecked())
    {
        enables |= LETTER;
    }
    else
    {
        enables &= ~LETTER;
    }

    if (useUpper->isChecked())
    {
        enables |= UPPER;
    }
    else
    {
        enables &= ~UPPER;
    }

    if (useNumbers->isChecked())
    {
        enables |= NUMBER;
    }
    else
    {
        enables &= ~NUMBER;
    }

    if (useSpecial->isChecked())
    {
        enables |= SPECIAL;
    }
    else
    {
        enables &= ~SPECIAL;
    }

    maxlength = maxEdit->text().toInt();
    show_pass = showPass->isChecked();
    emit passShowed(show_pass);
}

bool
PassGen::getShowPass()
{
    return show_pass;
}

int
PassGen::getSize()
{
    return maxlength + 1;
}

int
PassGen::getNextIndex(
        chartype *passtype)
{
    int index = qrand() % maxlength;

    //Check that we can have an index
    bool found = false;
    for (int i = 0; i < maxlength; i++)
    {
        if (passtype[i] != NONE)
        {
            found = true;
            break;
        }
    }

    //If no free just return a random index
    if (!found)
    {
        return index;
    }

    //Find a random index
    while (passtype[index] != NONE)
    {
        index = qrand() % maxlength;
    }

    return index;
}

void
PassGen::generate(
        char *result)
{
    chartype *passtype = (chartype*)calloc(1, sizeof(chartype) * maxlength);

    //Make sure we null terminate
    result[0] = '\0';

    //Done
    if ((enables == NONE) || (maxlength == 0))
    {
        free(passtype);
        return;
    }

    //Make sure we get 1 of each enable
    for (int i = 0; i < num_enables; i++)
    {
        passtype[getNextIndex(passtype)] = (chartype)(enables & (1 << i));
    }

    //Fill up the rest
    for (int i = 0; i < maxlength; i++)
    {
        if (passtype[i] == NONE)
        {
            chartype type = (chartype)(1 << (qrand() % num_enables));

            while ((enables & type) == 0)
            {
                type = (chartype)(1 << (qrand() % num_enables));
            }

            passtype[i] = type;
        }
    }

    //Generate types
    for (int i = 0; i < maxlength; i++)
    {
        switch(passtype[i])
        {
            case UPPER:
                result[i] = (char)((qrand() % 26) + 65); //A-Z
                break;

            case LETTER:
                result[i] = (char)((qrand() % 26) + 97); //a-z
                break;

            case NUMBER:
                result[i] = (char)((qrand() % 10) + 48); //0-9
                break;

            case SPECIAL:
            {
                char special_lut[] = {'!', '@', '#', '$', '%', '^', '&', '*'};
                result[i] = special_lut[(qrand() % (sizeof(special_lut) / sizeof(special_lut[0])))];
                break;
            }

            default:
                result[i] = '.';
                break;
        }
    }

    free(passtype);

    result[maxlength] = '\0';
}
